﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Ink;
using System.Windows.Input;
using BitsOfStuff.Support;

namespace BitsOfStuff
{
    /// <summary>
    /// This class provide conversion from strokes to position sequence
    /// </summary>
    public static class FootstepRecognition
    {
        // ReSharper disable UnassignedField.Local
        private static List<List<MySelectedPoint>> _mySelectedPoints;
        // ReSharper restore UnassignedField.Local

        private static List<Stroke> _strokes;

        // ReSharper disable UnassignedField.Local
        private static int[] _resultThread;
        // ReSharper restore UnassignedField.Local

        private static Rect _actualRectangle;

        private static MyVector _topVector;
        private static MyVector _leftVector;

        private static List<int> _footStep;

        private static List<int> _footStepMask;

        private static List<List<int>> _posibleFootsteps;
#pragma warning disable 169
        private static int[] _posibleFootStepsUsed;

        private static int _round;
        private static int _maxround;
        private static Stack<List<int>> _posiblePermutationIndexes;
#pragma warning restore 169
        private static Stack<string> _allFootStep;
        private static Stack<string> _allFootStepBackUp;

        private static double _xSegment;
        private static double _ySegment;

        private static double _xSegmentMask;
        private static double _ySegmentMask;

        private static bool _firsStroke;

        private static void addNonFirsStroke(Stroke stroke)
        {
            var strokeBourder = stroke.GetBounds();
            _actualRectangle = Rect.Union(_actualRectangle, strokeBourder);
            SupportDrawnFunctions.drawnRectangle(_actualRectangle);
        }

        private static void addFirsStroke(Stroke stroke)
        {
            _actualRectangle = stroke.GetBounds();
            SupportDrawnFunctions.drawnRectangle(_actualRectangle);
        }

        /// <summary>
        /// Function for adding stroke for footstep analysis
        /// </summary>
        /// <param name="stroke">new added stroke</param>
        public static void addStroke(Stroke stroke)
        {
            if (_allFootStep.Count == 0)
            {
                _allFootStepBackUp = new Stack<string>();
            }
            _strokes.Add(stroke);
            if (_firsStroke)
            {
                addFirsStroke(stroke);
                _firsStroke = false;
            }
            else
            {
                addNonFirsStroke(stroke);
            }
            makeGrid();
            reCalculateFootStep();
            _allFootStep.Push(SupportFunctions.convertFromSequenceToString(_footStep));
        }

        /// <summary>
        /// Refresh all internal parst
        /// </summary>
        public static void reInitial()
        {
            _firsStroke = true;
            _strokes = new List<Stroke>();
            _actualRectangle = new Rect();
            _footStep = new List<int>();
            _footStepMask = new List<int>();
            _allFootStep = new Stack<string>();
            _allFootStepBackUp = new Stack<string>();
            _posiblePermutationIndexes = new Stack<List<int>>();
            _posibleFootsteps = new List<List<int>>();
        }

        /// <summary>
        /// Function for initializing this class
        /// </summary>
        public static void initial()
        {
            _firsStroke = true;
            _strokes.Clear();
            _actualRectangle = new Rect();
            _footStep.Clear();
            _allFootStep.Clear();
        }

        private static void makeGrid()
        {
            _xSegment = makeOneAxeSegment(_actualRectangle.Width, _actualRectangle.Height);
            _ySegment = makeOneAxeSegment(_actualRectangle.Height, _actualRectangle.Width);
            _xSegmentMask = _xSegment / MyParameters.footSectorCount;
            _ySegmentMask = _ySegment / MyParameters.footSectorCount;
            makeVectors();
        }

        private static double makeOneAxeSegment(double size, double otherSize)
        {
            const double radianAngle = MyParameters._shapeAxesDiversion * (Math.PI / 180);
            double minimalSize = Math.Tan(radianAngle) * otherSize;

            int segmentCount = MyParameters.footSectorCount;

            //            for (int i = 1; i < segmentCount; i++)
            //          {
            if (size < /*(i) * */minimalSize)
                //            {
                segmentCount = 1;
            //          }
            //}

            return size / segmentCount;
        }

        private static void reCalculateFootStep()
        {
            _footStep.Clear();
            _footStepMask.Clear();
            _posibleFootsteps.Clear();
            foreach (Stroke s in _strokes)
            {
                calculateFootStepStroke(s);
            }
            Debug.WriteLine(String.Format("FootStepRaw    : {0}", SupportFunctions.convertFromSequenceToString(_footStep)));
            Debug.WriteLine(String.Format("FootStepMaskRaw: {0}", SupportFunctions.convertFromSequenceToString(_footStepMask)));
        }

        private static void calculateFootStepStroke(Stroke stroke)
        {
            int maskIndex;
            _footStep.Add(calculateFootStepPoint(stroke.StylusPoints.FirstOrDefault(), out maskIndex));
            _footStepMask.Add(maskIndex);

            //Posible intersect points
            var intersectPoint = SupportFunctions.findIntersect(stroke);

            foreach (int index in intersectPoint)
            {
                var tmp = new MyPoint();
                tmp.create(stroke.StylusPoints[index]);
                SupportDrawnFunctions.drawnPoint(MyParameters.inkCanvas, tmp, System.Windows.Media.Colors.MediumVioletRed);
                _footStep.Add(calculateFootStepPoint(stroke.StylusPoints[index], out maskIndex));
                _footStepMask.Add(maskIndex);
            }

            _footStep.Add(calculateFootStepPoint(stroke.StylusPoints.LastOrDefault(), out maskIndex));
            _footStepMask.Add(maskIndex);
            _footStep.Add(-1);
            _footStepMask.Add(-1);
        }

        /// <summary>
        /// Function that provide footstep sequence not for storke, but for points
        /// </summary>
        /// <param name="selectedPoints">Points for footstep analysion</param>
        public static void addSelectedPoints(List<MySelectedPoint> selectedPoints)
        {
            _mySelectedPoints.Add(selectedPoints);
        }

        // ReSharper disable UnusedMember.Local
        private static void calculateFootStepThread(List<MySelectedPoint> selectedPoints, int resultIndex)
        // ReSharper restore UnusedMember.Local
        {
            for (int i = 0; i < selectedPoints.Count; i++)
            {
                _resultThread[resultIndex + i] = calculateFootStepPoint(selectedPoints[i]);
            }
            _resultThread[resultIndex + selectedPoints.Count] = -1;
        }

        private static int calculateFootStepPoint(MySelectedPoint point)
        {
            double relativeX = _actualRectangle.X - point.getPoint().getX();
            double relativeY = _actualRectangle.Y - point.getPoint().getY();
            var xIndex = (int)(relativeX / _xSegment);
            var yIndex = (int)(relativeY / _ySegment);

            return xIndex + 3 * yIndex;
        }

        private static int calculateFootStepPoint(StylusPoint point, out int maskIndex)
        {
            double relativeX = _leftVector.distance(point);
            double relativeY = _topVector.distance(point);
            var xIndex = (int)(relativeX / _xSegment);
            var yIndex = (int)(relativeY / _ySegment);

            maskIndex = calculateFootStepPointMask(point, xIndex, yIndex);
            return xIndex + 3 * yIndex;
        }

        private static int calculateFootStepPointMask(StylusPoint point, int xIndex, int yIndex)
        {
            var startBorderX = Math.Abs(point.X - _actualRectangle.X - xIndex * _xSegment);
            var startBorderY = Math.Abs(point.Y - _actualRectangle.Y - yIndex * _ySegment);

            var xIndexMask = (int)(startBorderX / _xSegmentMask);
            var yIndexMask = (int)(startBorderY / _ySegmentMask);

            return xIndexMask + 3 * yIndexMask;
        }

        /// <summary>
        /// Function for getting footstep for actual strokes
        /// </summary>
        /// <returns></returns>
        public static string getFootStepString()
        {
            return _allFootStep.Peek();
        }

        /// <summary>
        /// Function that inform us that no other alternatives footstep is awayable
        /// </summary>
        /// <returns>bool value of zero alternative footsteps</returns>
        public static bool haveNoFootStepToRecord()
        {
            if (_allFootStep.Count == 0)
                return false;
            return true;
        }

        /// <summary>
        /// Function that inform us that one last alternatives footstep is awayable
        /// </summary>
        /// <returns>bool value of last alternative footstep</returns>
        public static bool isLastFootStepToRecord()
        {
            if (_allFootStep.Count == 1)
                return true;
            return false;
        }

        /// <summary>
        /// Function that give us footsteps for recording it to user profile
        /// </summary>
        /// <returns>footstep for record</returns>
        public static string getFootStepStringRecord()
        {
            var result = _allFootStep.Pop();
            return result;
        }

        /// <summary>
        /// Function for getting alternative footsteps
        /// </summary>
        /// <returns></returns>
        public static string getAlternativeFootStepString()
        {
            if (_posibleFootsteps == null || _posibleFootsteps.Count == 0)
            {
                firsCalculateFs();
            }

            if (_posiblePermutationIndexes.Count > 0)
            {
                var result = SupportFunctions.convertFromSequenceToString(_posiblePermutationIndexes.Pop());
                Debug.WriteLine(String.Format("Sendging alternative footsteps: {0}", result));
                return result;
            }
            return null;
        }

        private static void firsCalculateFs()
        {
            _posibleFootsteps = calculatePosibleFoostreps();
            var allPosibleSequenceInBadFormat = SupportFunctions.cartesianProduct(_posibleFootsteps);
            allPosibleSequenceInBadFormat = allPosibleSequenceInBadFormat.ToList();
            var allPosibleSequence = allPosibleSequenceInBadFormat.Select(enumerable => enumerable.ToList()).ToList();
            List<int> removedIntersect;
            if (removeIntersectPoint(out removedIntersect))
                _posiblePermutationIndexes.Push(removedIntersect);
            foreach (List<int> list in allPosibleSequence)
            {
                _posiblePermutationIndexes.Push(list);
            }
        }

        /// <summary>
        /// Function for cleaning intersect point from footsteps
        /// </summary>
        /// <param name="removed">footsteps that dont contain intersect point</param>
        /// <returns>status, if some intersect points was found</returns>
        public static bool removeIntersectPoint(out List<int> removed)
        {
            removed = new List<int>(_footStep.Count);
            var state = false;
            int i = 0;
            while (i <= _footStep.Count - 2)
            {
                removed.Add(_footStep[i]);
                i++;
                if (_footStep[i + 1] == -1)
                {
                    removed.Add(_footStep[i]);
                    removed.Add(_footStep[i + 1]);
                    i = i + 2;
                }
                else
                {
                    state = true;
                    while (i < _footStep.Count && _footStep[i] != -1)
                    {
                        i++;
                    }
                    removed.Add(_footStep[i - 1]);
                    removed.Add(_footStep[i]);
                    i++;
                }
            }

            return state;
        }

        /// <summary>
        /// Function that inform us that no alternative footsteps is on heap
        /// </summary>
        /// <returns>bool value of emptyness of alternative foosteps</returns>
        public static bool haveNoAlternativeFootSteps()
        {
            if (_posiblePermutationIndexes.Count == 0)
                return false;
            return true;
        }

        /// <summary>
        /// Funstion for transformation of footstep based on mask directions
        /// </summary>
        /// <param name="footstep">footstap to transform</param>
        /// <param name="x">x direction</param>
        /// <param name="y">y direction</param>
        /// <returns>transformed footstep</returns>
        public static int moveFootstep(int footstep, int x, int y)
        {
            //NO TRANSFORM FOR DELIMITER :)
            if (footstep == -1)
                return -1;

            //TOP BORDER
            if (footstep == 0 || footstep == 1 || footstep == 2)
                if (y > 0)
                    return footstep;

            //BOTTOM BORDER
            if (footstep == 6 || footstep == 7 || footstep == 8)
                if (y < 0)
                    return footstep;

            //LEFT BORDER
            if (footstep == 0 || footstep == 3 || footstep == 6)
                if (x < 0)
                    return footstep;

            //RIGHT BORDER
            if (footstep == 2 || footstep == 5 || footstep == 8)
                if (x > 0)
                    return footstep;

            y = y * -1;  // CHANGE DIRECTION OF Y AXES

            return (footstep + x) + (3 * y);
        }

        /// <summary>
        /// Function for transforming footstapr based on mask
        /// </summary>
        /// <param name="footstep">footstep</param>
        /// <param name="mask">footsep mask</param>
        /// <returns>transformed foostep</returns>
        public static List<int> transform(int footstep, int mask)
        {
            var result = new HashSet<int> { footstep };
            var xes = new List<int>(3) { 0 };
            var yes = new List<int>(3) { 0 };
            // TOP
            if (mask == 0 || mask == 1 || mask == 2)
            {
                yes.Add(1);
            }
            // BOTTOM
            if (mask == 6 || mask == 7 || mask == 8)
            {
                yes.Add(-1);
            }
            // LEFT
            if (mask == 0 || mask == 3 || mask == 6)
            {
                xes.Add(-1);
            }
            // RIGHT
            if (mask == 2 || mask == 5 || mask == 8)
            {
                xes.Add(1);
            }
            foreach (int y in yes)
            {
                foreach (var x in xes)
                {
                    result.Add(moveFootstep(footstep, x, y));
                }
            }

            return result.ToList();
        }

        private static List<List<int>> calculatePosibleFoostreps()
        {
            return _footStep.Select((step, i) => transform(step, _footStepMask[i])).ToList();
        }

        private static void makeVectors()
        {
            var leftBottom = new MyPoint();
            leftBottom.create(_actualRectangle.BottomLeft.X, _actualRectangle.BottomLeft.Y);
            //Create center of rectangle;
            //Create left top point and rotate
            var leftTop = new MyPoint();
            leftTop.createAndRotate(_actualRectangle.TopLeft.X, _actualRectangle.TopLeft.Y, leftBottom, MyParameters.shapeStartAngle - MyParameters._shapeStartAngle);

            //Create left bottom point and rotate

            //Create right bottom point and rotate
            var rightTop = new MyPoint();
            rightTop.createAndRotate(_actualRectangle.TopRight.X, _actualRectangle.TopRight.Y, leftBottom, MyParameters.shapeStartAngle - MyParameters._shapeStartAngle);

            _leftVector = new MyVector();
            _leftVector.makeVector(leftTop, leftBottom);

            _topVector = new MyVector();
            _topVector.makeVector(leftTop, rightTop);
        }

        /// <summary>
        /// Function that give us footstep of some point, but this point have no efect of other strokes
        /// </summary>
        /// <param name="stylusPoint">Input point</param>
        /// <returns>point footstep</returns>
        public static int peekPoint(StylusPoint stylusPoint)
        {
            int nothing;
            return calculateFootStepPoint(stylusPoint, out nothing);
        }

        /// <summary>
        /// give us last footstep
        /// </summary>
        public static void corectMistake()
        {
            _allFootStep = new Stack<string>(_allFootStepBackUp);
        }
    }
}